#!/usr/bin/python

# add the SmartMeshSDK/ folder to the path
import os
import sys
import traceback
import struct
import csv

temp_path = sys.path[0]
if temp_path:
    sys.path.insert(0, os.path.join(temp_path, 'experimental'))
    sys.path.insert(0, os.path.join(temp_path, 'dustUI'))
    sys.path.insert(0, os.path.join(temp_path, 'SmartMeshSDK'))

# verify installation
import SmsdkInstallVerifier
(goodToGo,reason) = SmsdkInstallVerifier.verifyComponents(
                            [
                                SmsdkInstallVerifier.PYTHON,
                                SmsdkInstallVerifier.PYSERIAL,
                            ]
                        )
if not goodToGo:
    print "Your installation does not allow this application to run:\n"
    print reason
    raw_input("Press any button to exit")
    sys.exit(1)

#import Tkinter
import threading
import copy
import time
import datetime

import logging
import logging.handlers

import dustStyle
from dustWindow              import dustWindow
from dustFrameApi            import dustFrameApi
from dustFrameConnection     import dustFrameConnection
from dustFrameMoteList       import dustFrameMoteList
from dustFrameText           import dustFrameText

from ApiDefinition           import IpMgrDefinition
from ApiDefinition           import HartMgrDefinition
from ApiException            import APIError

from IpMgrConnectorSerial    import IpMgrConnectorSerial
from IpMgrConnectorMux       import IpMgrSubscribe,   \
                                    IpMgrConnectorMux
from IpMgrConnectorMux       import LatencyCalculator

from oap                     import OAPDispatcher
from oap                     import OAPClient
from oap                     import OAPMessage
from oap                     import OAPNotif

GUI_UPDATEPERIOD = 1000   # in ms
OAP_PORT     = 0xee48

# columns names
COL_NOTIF_DATA   = 'Packets'
COL_NOTIF_IPDATA = IpMgrSubscribe.IpMgrSubscribe.NOTIFIPDATA
COL_NOTIF_HR     = IpMgrSubscribe.IpMgrSubscribe.NOTIFHR
COL_NEO_TIME    = 'Time (utcSecs)'
COL_NEO_VALS    = 'Payload'


import logging
class NullHandler(logging.Handler):
    def emit(self, record):
        pass
log = logging.getLogger('neoMonitor')
log.setLevel(logging.DEBUG)
log.addHandler(NullHandler())

class notifClient(object):
    '''
    \ingroup MgrListener
    '''
    
    def __init__(self, apiDef, connector, disconnectedCallback, latencyCalculator):
        
        # store params
        self.apiDef               = apiDef
        self.connector            = connector
        self.disconnectedCallback = disconnectedCallback
        self.latencyCalculator    = latencyCalculator
        
        # log
        log.debug("Initialize notifClient")
        
        # variables
        self.dataLock             = threading.Lock()
        self.isMoteActive         = {}
        self.data                 = {}
        self.updates              = {}
        
        # subscriber
        if   isinstance(self.apiDef,IpMgrDefinition.IpMgrDefinition):
            # we are connected to an IP manager
            
            self.subscriber = IpMgrSubscribe.IpMgrSubscribe(self.connector)
            self.subscriber.start()
            self.subscriber.subscribe([
                                        IpMgrSubscribe.IpMgrSubscribe.NOTIFDATA,
                                      ],
                                      self._dataCallback,
                                      True)
            self.subscriber.subscribe([
                                        IpMgrSubscribe.IpMgrSubscribe.NOTIFEVENT,
                                      ],
                                      self._eventCallback,
                                      True)
            self.subscriber.subscribe([IpMgrSubscribe.IpMgrSubscribe.ERROR,
                                       IpMgrSubscribe.IpMgrSubscribe.FINISH],
                                       self.disconnectedCallback,
                                       True)
        
        else:
            output = "apiDef of type {0} unexpected".format(type(self.apiDef))
            log.critical(output)
            print output
            raise SystemError(output)
        
        # OAP dispatcher
        self.oap_dispatch = OAPDispatcher.OAPDispatcher()
        self.oap_dispatch.register_notif_handler(self._handle_oap_notif)
    
    #======================== public ==========================================
    
    def getData(self):
        self.dataLock.acquire()
        returnIsMoteActive   = copy.deepcopy(self.isMoteActive)
        returnData           = copy.deepcopy(self.data)
        returnUpdates        = copy.deepcopy(self.updates)
        self.updates         = {}
        self.dataLock.release()
        return (returnIsMoteActive,returnData,returnUpdates)
    
    def getOapDispatcher(self):
        return self.oap_dispatch
    
    def clearNotifCounters(self,mac):
        self.dataLock.acquire()
        self.updates = {}
        if mac in self.data:
            self.updates[mac] = []
            for k,v in self.data[mac].items():
                if   k in [COL_NOTIF_DATA,
                           COL_NOTIF_IPDATA,
                           COL_NOTIF_HR]:
                    self.updates[mac].append(k)
                    self.data[mac][k] = 0
                elif k in [COL_NEO_TIME,
                           COL_NEO_VALS,]:
                    self.updates[mac].append(k)
                    self.data[mac][k] = '-'
        self.dataLock.release()
    
    def clearTempCounters(self,mac):
        self.dataLock.acquire()
        self.updates = {}
        if mac in self.data:
            self.updates[mac] = []
            for k,v in self.data[mac].items():
                if   k in [COL_TEMP_NUM,]:
                    self.updates[mac].append(k)
                    self.data[mac][k] = 0
        self.dataLock.release()
        
    def disconnect(self):
        self.connector.disconnect()
    
    #======================== private =========================================
    
    def _dataCallback(self, notifName, notifParams):
        
        # log
        log.debug("notifClient._dataCallback {0} {1}".format(notifName, notifParams))
        
        timeNow = time.time()
        
        # read MAC address from notification
        mac = self._getMacFromNotifParams(notifParams)
        mac_address = '-'.join(['%.2x'%b for b in mac])
        if mac_address <> "00-17-0d-00-00-3f-f9-35":
            # lock the data structure
            self.dataLock.acquire()

            # add mac/type to data, if necessary
            if mac not in self.data:
                self.data[mac] = {}
            if notifName not in self.data[mac]:
                self.data[mac][notifName] = 0

            # add mac/type to updates, if necessary
            if mac not in self.updates:
                self.updates[mac] = []
            if notifName not in self.updates[mac]:
                self.updates[mac].append(notifName)

            # increment counter
            self.data[mac][notifName] += 1

            if COL_NEO_TIME not in self.data[mac]:
                self.data[mac][COL_NEO_TIME] = '-'
            if COL_NEO_TIME not in self.updates[mac]:
                self.updates[mac].append(COL_NEO_TIME)

            self.data[mac][COL_NEO_TIME] = str(notifParams.utcSecs)

            if COL_NEO_VALS not in self.data[mac]:
                self.data[mac][COL_NEO_VALS] = '-'
            if COL_NEO_VALS not in self.updates[mac]:
                self.updates[mac].append(COL_NEO_VALS)

            self.data[mac][COL_NEO_VALS] = str(notifParams.data)

            #========== get ICM data ================
            try:
                icm_data = notifParams.data
                #print icm_data
                if len(icm_data) == 64:
                    d_iterable = iter(xrange(len(icm_data)-4))
                    f = open(os.path.join(temp_path, 'data/') +'all_data.csv','a+')
                    f_current = open(os.path.join(temp_path, 'data/') + 'current_data.csv','a+')
                    writer = csv.writer(f)
                    writer_current = csv.writer(f_current)
                    line = [mac_address, int(timeNow)]
                    print line
                    for i in d_iterable:
                        channel_value = (icm_data[i+1] << 24) + (icm_data[i+2] << 16) + (icm_data[i+3] << 8) + icm_data[i+4]
                        line.append(channel_value)
                        print "channel " + str(icm_data[i]) + " = " + str(channel_value)
                        [d_iterable.next() for x in range(4)]
                    writer.writerow(line)
                    writer_current.writerow(line)
                    f.close()
                    f_current.close()
                else:
                    print "incomplete data for mote: " + mac_address
            except Exception as err:
                output = traceback.format_exc()
                print output
                log.critical(output)
                f.close()
                f_current.close()

            # unlock the data structure
            self.dataLock.release()

            # parse OAP packet
            if notifName in [IpMgrSubscribe.IpMgrSubscribe.NOTIFDATA]:
                self.oap_dispatch.dispatch_pkt(notifName, notifParams)
    
    def _eventCallback(self, notifName, notifParams):
        try:
            # log
            log.debug("notifClient._eventCallback {0} {1}".format(notifName, notifParams))
            
            # lock the data structure
            self.dataLock.acquire()
            
            if   isinstance(self.apiDef,IpMgrDefinition.IpMgrDefinition):
                
                if notifName in [IpMgrSubscribe.IpMgrSubscribe.EVENTMOTEOPER]:
                    mac = self._getMacFromNotifParams(notifParams)
                    self.isMoteActive[mac] = True
                    
                if notifName in [IpMgrSubscribe.IpMgrSubscribe.EVENTMOTELOST]:
                    mac = self._getMacFromNotifParams(notifParams)
                    self.isMoteActive[mac] = False
                
            elif isinstance(self.apiDef,HartMgrDefinition.HartMgrDefinition):
                
                if notifName in ['MoteLive']:
                    mac = self._getMacFromNotifParams(notifParams)
                    self.isMoteActive[mac] = True
                
                if notifName in ['MoteUnknown','MoteDisconnect','MoteJoin']:
                    mac = self._getMacFromNotifParams(notifParams)
                    self.isMoteActive[mac] = False
                
            else:
                output = "apiDef of type {0} unexpected".format(type(self.apiDef))
                #log.critical(output)
                print output
                raise SystemError(output)
        
        except Exception as err:
            output = traceback.format_exc()
            print output
            log.critical(output)
        
        finally:
            
            # unlock the data structure
            self.dataLock.release()
    
    def _getMacFromNotifParams(self,notifParams):
        
        if   isinstance(self.apiDef,IpMgrDefinition.IpMgrDefinition):
            # we are connected to an IP manager
            
            return tuple(notifParams.mac)
        #elif isinstance(self.apiDef,HartMgrDefinition.HartMgrDefinition):
            # we are connected to a HART manager
            
        #    return tuple([int(i,16) for i in notifParams['macAddr'].split('-')])
        else:
            output = "apiDef of type {0} unexpected".format(type(self.apiDef))
            #log.critical(output)
            print output
            raise SystemError(output)
    
    def _handle_oap_notif(self,mac,notif):
        
        # convert MAC to tuple
        mac = tuple(mac)
        
        if isinstance(notif,OAPNotif.OAPTempSample):
            # this is a temperature notification
            
            # lock the data structure
            self.dataLock.acquire()
            
            # add mac/type to updates, if necessary
            if mac not in self.data:
                self.data[mac] = {}
            if COL_TEMPERATURE not in self.data[mac]:
                self.data[mac][COL_TEMPERATURE] = None
            if COL_TEMP_NUM not in self.data[mac]:
                self.data[mac][COL_TEMP_NUM]   = 0
            
            # add mac/type to updates, if necessary
            if mac not in self.updates:
                self.updates[mac] = []
            if COL_TEMPERATURE not in self.updates[mac]:
                self.updates[mac].append(COL_TEMPERATURE)
            if COL_TEMP_NUM not in self.updates[mac]:
                self.updates[mac].append(COL_TEMP_NUM)
            
            self.data[mac][COL_TEMPERATURE]  = notif.samples[0]
            self.data[mac][COL_TEMP_NUM]   += 1
            
            # unlock the data structure
            self.dataLock.release()

class tempMonitorGui(object):
   
    def __init__(self):
        
        # local variables
        self.guiLock            = threading.Lock()
        self.apiDef             = IpMgrDefinition.IpMgrDefinition()
        self.notifClientHandler = None
        self.latencyCalculator  = None
        self.guiUpdaters        = 0
        self.oap_clients        = {}

        # create window
        #self.window = dustWindow('neoMonitor',
        #                         self._windowCb_close)
        # add a API selection frame
        #self.apiFrame = dustFrameApi(
        #                            self.window,
        #                            self.guiLock,
        #                            self._apiFrameCb_apiLoaded,
        #                            row=0,column=0,
        #                            deviceType=dustFrameApi.MANAGER)
        #self.apiFrame.show()
       
        # add a connection frame
        #self.connectionFrame = dustFrameConnection(
        #                            self.window,
        #                            self.guiLock,
        #                            self._connectionFrameCb_connected,
        #                            frameName="manager connection",
        #                            row=1,column=0)
        
        # add a mote list frame
        #columnnames =       [
        #                        # NeoMoteClock
        #                        {
        #                            'name': COL_NEO_TIME,
        #                            'type': dustFrameMoteList.LABEL,
        #                        },
        #                        # NeoMoteVals
        #                        {
        #                            'name': COL_NEO_VALS,
        #                            'type': dustFrameMoteList.LABEL,
        #                        },
        #                    ]
        #self.moteListFrame = dustFrameMoteList(self.window,
        #                                       self.guiLock,
        #                                       columnnames,
        #                                       row=2,column=0)
        #self.moteListFrame.show()
        
        # add a status (text) frame
        #self.statusFrame   = dustFrameText(
        #                            self.window,
        #                            self.guiLock,
        #                            frameName="status",
        #                            row=3,column=0)
        #self.statusFrame.show()

    #===================== bpet code fills in user info ================================

        self.apiDef    = IpMgrDefinition.IpMgrDefinition()
        self._apiFrameCb_apiLoaded(self.apiDef)
        self.connector = IpMgrConnectorSerial.IpMgrConnectorSerial()

        #assign com port here
        connectParams = {
            'port': '/dev/ttyUSB3',
        }
        self.connector.connect(connectParams)
        self._connectionFrameCb_connected(self.connector)
    
    #======================== public ==========================================
    
    def start(self):
        
        # log
        log.debug("Starting tempMonitorGui")
        
        # start Tkinter's main thead
        #try:
        #    self.window.mainloop()
        #except SystemExit:
        #    sys.exit()

    #======================== private =========================================
    
    #===== user interaction
    
    def _apiFrameCb_apiLoaded(self,apiDefLoaded):
        '''
        \brief Called when an API is selected.
        '''
        
        # log
        #log.debug("_apiFrameCb_apiLoaded")
        #print apiDefLoaded
        # record the loaded API
        self.apiDef = apiDefLoaded
        
        # tell other frames about it
        #self.connectionFrame.apiLoaded(self.apiDef)
        
        # display frames
        #self.connectionFrame.show()
        
        # update status
        #self.statusFrame.write("API {0} loaded successfully.".format(type(apiDefLoaded)))
    
    def _connectionFrameCb_connected(self,connector):
        '''
        \brief Called when the connectionFrame has connected.
        '''
        #print connector
        # log
        #log.debug("_connectionFrameCb_connected")
        
        # store the connector
        self.connector = connector
        
        # start a latency calculator
        self.latencyCalculator = LatencyCalculator.LatencyCalculator(self.apiDef,self.connector)
        self.latencyCalculator.start()
        
        # start a notification client
        self.notifClientHandler = notifClient(
                    self.apiDef,
                    self.connector,
                    self._connectionFrameCb_disconnected,
                    self.latencyCalculator,
                )
        
        # retrieve list of motes from manager
        macs = self._getOperationalMotesMacAddresses()
        for mac in macs:
            self._addNewMote(mac)
        
        # clear the colors on the GUI
        #self.moteListFrame.clearColors()
        
        # schedule the GUI to update itself in GUI_UPDATEPERIOD ms
        #if self.guiUpdaters==0:
        #    self.moteListFrame.after(GUI_UPDATEPERIOD,self._updateMoteList)
        #    self.guiUpdaters += 1
        
        # update status
        #self.statusFrame.write("Connection to manager successful.")

 
    
    def _connectionFrameCb_disconnected(self,notifName=None,notifParams=None):
        '''
        \brief Called when the connectionFrame has disconnected.
        '''
        
        # kill the latency calculator thread
        if self.latencyCalculator:
            self.latencyCalculator.disconnect()
            self.latencyCalculator = None
        
        # update the GUI
        #self.connectionFrame.updateGuiDisconnected()
        
        # delete the connector
        self.connector = None
    
    def _windowCb_close(self):
        if self.latencyCalculator:
            self.latencyCalculator.disconnect()
        if self.notifClientHandler:
            self.notifClientHandler.disconnect()
    
    #===== helpers
    
    def _getOperationalMotesMacAddresses(self):
        returnVal = []
        
        if   isinstance(self.apiDef,IpMgrDefinition.IpMgrDefinition):
            # we are connected to an IP manager
            
            currentMac     = (0,0,0,0,0,0,0,0) # start getMoteCfg() iteration with the 0 MAC address
            continueAsking = True
            while continueAsking:
                try:
                    res = self.connector.dn_getMoteCfg(currentMac,1)
                except APIError:
                    continueAsking = False
                else:
                    if ((not res.isAP) and (res.moteState in [4,])):
                        returnVal.append(tuple(res.mac))
                    currentMac = res.mac
        
        elif isinstance(self.apiDef,HartMgrDefinition.HartMgrDefinition):
            # we are connected to a HART manager
            
            for m in self.connector.dn_getMotes():
                if ((not m.isAccessPoint) and (m.state in ['Operational',])):
                    returnVal.append(tuple([int(i,16) for i in m.macAddr.split('-')]))
        
        else:
            output = "apiDef of type {0} unexpected".format(type(self.apiDef))
            #log.critical(output)
            print output
            raise SystemError(output)
        
        # order by increasing MAC address
        returnVal.sort()
        
        return returnVal
    
    def _addNewMote(self,mac):
    
        # add mote to GUI
        # Note: if you're reconnecting, mote already exists
        
        columnvals = {
            #neo-specific vals
            COL_NEO_TIME:            '-',
            COL_NEO_VALS:            '-',
        }
        
        #if mac not in self.oap_clients:
        #    self.moteListFrame.addMote(
        #            mac,
        #            columnvals,
        #        )
        
        # create OAPClient
        # Note: if you're reconnecting, this recreates the OAP client
        self.oap_clients[mac] = OAPClient.OAPClient(mac,
                                                    self._sendDataToConnector,
                                                    self.notifClientHandler.getOapDispatcher())
    
   
    def _updateMoteList(self):
        
        updatable_columns = [
                                COL_NOTIF_DATA,
                                COL_NEO_TIME,
                                COL_NEO_VALS,
                            ]
        
        # get the data
        (isMoteActive,data,updates) = self.notifClientHandler.getData()
        
        # update the frame
        for mac,data in data.items():
            
            # detect new motes
            if mac not in self.oap_clients:
                self._addNewMote(mac)
            
            # update
            for columnname,columnval in data.items():
                if columnname in updatable_columns:
                    if ((mac in updates) and (columnname in updates[mac])):
                        self.moteListFrame.update(mac,columnname,columnval)
        
        # enable/disable motes
        for mac in isMoteActive:
            if isMoteActive[mac]:
                self.moteListFrame.enableMote(mac)
            else:
                self.moteListFrame.disableMote(mac)
        
        # schedule the next update
        self.moteListFrame.after(GUI_UPDATEPERIOD,self._updateMoteList)
    
    def _sendDataToConnector(self,mac,priority,srcPort,dstPort,options,data):
        if   isinstance(self.apiDef,IpMgrDefinition.IpMgrDefinition):
            # we are connected to an IP manager
            
            self.connector.dn_sendData(
                mac,
                priority,
                srcPort,
                dstPort,
                options,
                data
            )
        elif isinstance(self.apiDef,HartMgrDefinition.HartMgrDefinition):
            # we are connected to a HART manager
            
            self.connector.dn_sendRequest(
                '-'.join(["%.2x"%b for b in mac]),    # macAddr  (string)
                'maintenance',                        # domain   (string)
                'low',                                # priority (string)
                True,                                 # reliable (string)
                [0x00,0x00,0xfc,0x12]+data,           # data     (hexdata)
            )
        else:
            output = "apiDef of type {0} unexpected".format(type(self.apiDef))
            #log.critical(output)
            print output
            raise SystemError(output)

#============================ logging =========================================

## Name of the log file
LOG_FILENAME       = 'tempMonitor.log'
## Format of the lines printed into the log file.
LOG_FORMAT         = "%(asctime)s [%(name)s:%(levelname)s] %(message)s"
## Handler called when a module logs some activity.
logHandler = logging.handlers.RotatingFileHandler(LOG_FILENAME,
                                               maxBytes=2000000,
                                               backupCount=5,
                                               mode='w'
                                               )
logHandler.setFormatter(logging.Formatter(LOG_FORMAT))
for loggerName in ['LatencyCalculator',
                   'tempMonitor']:
    temp = logging.getLogger(loggerName)
    temp.setLevel(logging.CRITICAL)
    temp.addHandler(logHandler)

#============================ main ============================================

def main():
    tempMonitorGuiHandler = tempMonitorGui()
    tempMonitorGuiHandler.start()

if __name__ == '__main__':
    main()
